﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Timers;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Graphics;
	using System.Diagnostics;
	using Z2.Common;

	#endregion

	/// <summary>
	/// Základní třída pro všechny objekty ve hře
	/// </summary>
	public class ObjectBase : IDisposable
	{

		#region Const

		public const int MODIFY_VECTOR_TICK = 10;
		public const float FRICTION_DEFAULT = 0.25f;
		private const string DefaultLight = "light";

		#endregion

		#region Members

		private long m_id;

		private ObjectStateHorizontal m_horizontalState; // Horizontální stav objektu
		private ObjectStateVertical m_verticalState; // Vertikální stav objektu

		protected Vector2 m_initialPosition; // Výchozí pozice objektu (nemění se)
		protected Vector2 m_position; // Pozice objektu
		protected Vector2 m_move; // Vektor, který mění m_position
		private Vector2 m_change; // Zde se ukládá hodnta pro změnu vektoru m_move v obou osách

		private Sprite m_sprite; // Sprite asociovaný s objektem
		private Light m_light;
		private DateTime m_modifyVectorTime;
		private DateTime m_normalizeVectorTime;

		private float m_friction; // Tření objektu
		private float m_maxSpeedHorizontal; // Max. rychlost v ose X

		private bool m_disposed;

		private ObjectType m_type; // Typ objektu
		private Rectangle m_collisionRect; // Kolizní rectangle
		private ObjectBehaviorBase m_behavior; // Definuje chování objektu
		private bool m_isStatic = false;
		protected bool m_isInitialized = false;
		private DateTime m_lifetime;

		#endregion

		#region Constructor

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="sprite"><see cref="Sprite"/></param>
		public ObjectBase(Sprite sprite)
		{
			this.m_position = new Vector2();
			this.m_sprite = sprite;

			this.m_friction = ObjectBase.FRICTION_DEFAULT;

			this.m_maxSpeedHorizontal = -1;
			this.ModifyVectorTime = ObjectBase.MODIFY_VECTOR_TICK;
			this.NormalizeVectorTime = 10;

			this.m_verticalState = ObjectStateVertical.Idle;
			this.m_horizontalState = ObjectStateHorizontal.Idle;

			this.m_collisionRect = new Rectangle(0, 0, this.m_sprite.FrameWidth, this.m_sprite.FrameHeight);

			this.m_light = new Light(ObjectBase.DefaultLight);
			this.Sprite.Depth = Depths.Object;
			this.CorrectPosition = Vector2.Zero;
		}

		#endregion

		#region Properties
		protected virtual void OnInWater()
		{
		}
		protected virtual void OnOutWater()
		{
		}
		private bool m_isInWater;
		public bool IsInWater
		{
			get { return this.m_isInWater; }
			set
			{
				if (value == this.m_isInWater) return;
				this.m_isInWater = value;
				if (value) { this.OnInWater(); } else { this.OnOutWater(); }
			}
		}

		protected Light Light
		{
			get { return this.m_light; }
			set { this.m_light = value; }
		}

		public long Id
		{
			get { return this.m_id; }
			set { this.m_id = value; }
		}

		public double NormalizeVectorTime { get; set; }

		public double ModifyVectorTime { get; set; }

		protected DateTime Lifetime
		{
			get { return m_lifetime; }
			set { m_lifetime  = value;}
		}

		public bool IsDestroyed
		{
			get;
			set;
		}

		public bool IsStatic
		{
			get { return this.m_isStatic; }
			set { this.m_isStatic = value; }
		}

        public Vector2 CorrectPosition { get; set; }

		/// <summary>
		/// Vrací vektor, který mění pohybový vektor <see cref="Move"/>
		/// </summary>
		internal Vector2 Change
		{
			get
			{
				return this.m_change;
			}
			set
			{
				this.m_change = value;
			}
		}

		/// <summary>
		/// Nastaví nebo vrátí "chování" objektu
		/// </summary>
		internal ObjectBehaviorBase Behavior
		{
			get
			{
				return this.m_behavior;
			}
			set
			{
				this.m_behavior = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje kolizní rectangle
		/// </summary>
		protected Rectangle CollisionRect
		{
			get
			{
				return this.m_collisionRect;
			}
			set
			{
				this.m_collisionRect = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje typ objektu
		/// </summary>
		public ObjectType Type
		{
			get
			{
				return this.m_type;
			}
			set
			{
				this.m_type = value;
			}
		}

		/// <summary>
		/// Vrací pohybový vektor, který mení pozici objektu.
		/// </summary>
		internal Vector2 Move
		{
			get
			{
				return this.m_move;
			}
			set
			{
				this.m_move = value;
			}
		}

		private Vector2 m_link = Vector2.Zero;
		public Vector2 Link
		{
			set
			{
				this.m_link = value;
			}
			get
			{
				return this.m_link;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje vertikální stav objektu
		/// </summary>
		public ObjectStateVertical VerticalState
		{
			get
			{
				return this.m_verticalState;
			}
			internal set
			{
				this.m_verticalState = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje horizontální stav objektu
		/// </summary>
		public ObjectStateHorizontal HorizontalState
		{
			get
			{
				return this.m_horizontalState;
			}
			internal set
			{
				this.m_horizontalState = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje tření
		/// </summary>
		public float Friction
		{
			get 
			{
				return this.m_friction;
			}
			set
			{
				this.m_friction = Math.Abs(value);
			}
		}

		/// <summary>
		/// Vrací sprite objektu
		/// </summary>
		public Sprite Sprite
		{
			get
			{
				return this.m_sprite;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje původní pozici objektu
		/// </summary>
		public Vector2 InitialPosition
		{
			get
			{
				return this.m_initialPosition;
			}
			set
			{
				this.m_initialPosition = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje pozici objektu
		/// </summary>
		public Vector2 Position
		{
			get
			{
				return this.m_position;
			}
			set
			{
				this.m_position = value;
				this.OnPositionChanged();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje maximální rychlost v ose X
		/// </summary>
		public float MaxHorizontalSpeed
		{
			get
			{
				return this.m_maxSpeedHorizontal;
			}
			set
			{
				this.m_maxSpeedHorizontal = value;
			}
		}

		#endregion

		#region Methods

		protected float Depth(float baseDepth)
		{
			return baseDepth + (GetHashCode() / 1000000000.0f);
		}

		public virtual void Update()
		{
			this.m_sprite.Update();
			float l = World.Current.GlobalLight;// *1.2F;
			//if (l > 1F) l = 1F;
			this.m_light.Color = new Color(this.m_light.Color, 255-Convert.ToByte(255F * l));
			//this.m_light.MultiplyLight = 10F/ ((1F/World.Current.GlobalLight));
			//if (this.m_light.MultiplyLight < 1F) this.m_light.MultiplyLight = 1F;

			this.ModifyVector();
			this.NormalizeVector();
		}

		public virtual void Initialize()
		{
			if (this.m_isInitialized) return;

			this.m_isInitialized = true;
			this.Lifetime = DateTime.Now;
		}

		protected virtual void OnPositionChanged()
		{
		}

		/// <summary>
		/// Metoad mění vektor <see cref="Move"/> v ose Y
		/// </summary>
		/// <param name="value">Hodnota</param>
		public void ChangeVertical(float value)
		{
			if (value != 0 && this.VerticalState == ObjectStateVertical.Idle)
			{
				this.m_verticalState = value > 0 ? ObjectStateVertical.Falling : ObjectStateVertical.Rising;
				this.OnStartMovingVertical();
			}

			this.m_move.Y = value;
		}

		/// <summary>
		/// Metoad mění vektor <see cref="Move"/> v ose X
		/// </summary>
		/// <param name="value">Hodnota</param>
		public void ChangeHorizontal(float value)
		{
			if (value > this.MaxHorizontalSpeed) value = this.MaxHorizontalSpeed;
			if (this.IsInWater || (value != 0 && this.HorizontalState == ObjectStateHorizontal.Idle))
			{
				this.m_horizontalState = value > 0 ? ObjectStateHorizontal.MovingRight : ObjectStateHorizontal.MovingLeft;
				this.OnStartMovingHorizontal();
			}

			this.m_move.X = value;
		}

		/// <summary>
		/// Pohyb objektem v horizontálním směru
		/// </summary>
		/// <param name="value">Hodnota pohybu</param>
		public void MoveHorizontal(float value)
		{
			// Pokud je X hodnota vektoru nulová (tzn., že objekt se nehýbe v ose X)
			if (this.IsInWater || (value != 0 && this.HorizontalState == ObjectStateHorizontal.Idle))
			{
				this.m_horizontalState = (this.m_move.X + value) > 0 ? 
					ObjectStateHorizontal.MovingRight : ObjectStateHorizontal.MovingLeft;
				// Začíná se pohybovat v ose X
				this.OnStartMovingHorizontal();
			}
			if (this.IsInWater) this.Sprite.StartAnimation();

			this.m_change.X = value; // Aktualizace vektoru
		}

		/// <summary>
		/// Pohyb objektem ve vertikálním směru
		/// </summary>
		/// <param name="value">Hodnota pohybu</param>
		public void MoveVertical(float value)
		{
			if (value != 0 && this.VerticalState == ObjectStateVertical.Idle)
			{
				this.m_verticalState = (this.m_move.Y + value) > 0 ? 
					ObjectStateVertical.Falling : ObjectStateVertical.Rising;
				// Pohybuje se v ose Y
				this.OnStartMovingVertical();
			}
			// Aktualizavce vektoru
			this.m_change.Y = value;
		}

		public virtual void DrawLight(SpriteBatch batch)
		{
			this.m_light.DrawLight(batch, Vector2.Add(this.Position, Camera.Instance.Position), this.Sprite);
		}

		/// <summary>
		/// Vykreslení objektu
		/// </summary>
		/// <param name="batch"><see cref="SpriteBatch"/></param>
		public virtual void Draw(SpriteBatch batch)
		{
			this.m_sprite.Draw(batch, Vector2.Add(this.m_position, Camera.Instance.Position));
		}

		/// <summary>
		/// Metoda otestuje tzv. falešnou kolizi v ose X. Používá se k pohybu objektu 
		/// směrem vzhůru, pokud je to možné. (=chození do kopce atp.).
		/// </summary>
		/// <param name="result">Výsledek předchozího testu kolize</param>
		/// <returns><strong>True</strong>, pokud se jedná o falešnou kolizi a objekt se může pohybovat směrem vzhůru.</returns>
		//internal bool CheckFalseCollision(IntersectionResult result)
		//{
		//    float val = (this.m_position.Y + this.m_sprite.Height) - result.Rectangle.Top;
		//    if ((result.With == null || result.With.Type == ObjectType.Bendable) &&
		//        val <= WorldField.SIZE && this.VerticalState == ObjectStateVertical.Idle)
		//    {
		//        this.m_position.Y = result.Rectangle.Top - this.m_sprite.Height - 2;
		//        return true;
		//    }
		//    return false;
		//}

		/// <summary>
		/// Metoda otesuje kolizi tohoto objektu s objektem <paramref name="objectBase"/>
		/// </summary>
		/// <param name="objectBase">Objekt, se kterým se testuje kolize.</param>
		/// <param name="rect">Kolizní rect tohoto objektu (modifikovaný)</param>
		/// <returns>Výsledek kolize</returns>
		public virtual IntersectionResult Intersects(ObjectBase objectBase, Rectangle rect)
		{
			//if (objectBase == null) return IntersectionResult.None;
			if (objectBase.CollisionRect.IsEmpty) return IntersectionResult.None;

			Rectangle objectBaseRectangle = objectBase.GetRectangle(0, 0);

			if (rect.Intersects(objectBaseRectangle))
			{
				IntersectionResult result = new IntersectionResult();
				result.Rectangle = objectBaseRectangle;
				result.HorizontalType = (rect.Center.X > objectBaseRectangle.Center.X) ? IntersectionHorizontalType.FromRight : IntersectionHorizontalType.FromLeft;
				result.VerticalType = (rect.Center.Y > objectBaseRectangle.Center.Y) ? IntersectionVerticalType.FromBottom : IntersectionVerticalType.FromTop;
				result.IsCollision = true;
				result.With = objectBase;

				objectBase.OnCollision(ref result, this);
				this.OnCollision(ref result, objectBase);

				return result;
			}

			return IntersectionResult.None;
		}

		protected virtual void OnCollision(ref IntersectionResult result, ObjectBase obj)
		{
			if (result.With != null)
			{
				result.With.Behavior.PerfomActionsOn(this, obj, result);
			}
		}

		public event EventHandler Destroyed;

		public virtual void Destroy()
		{
			Move = new Vector2();
			
			m_isInitialized = false;
			World.Current.UnregisterObject(this);
			if (Destroyed != null)
				Destroyed(this, EventArgs.Empty);
		}

		/// <summary>
		/// Metoda se volá při kolizi v ose X
		/// </summary>
		/// <param name="result">Výsledek testu kolize</param>
		internal virtual void OnHorizontalCollision(IntersectionResult result)
		{
			//if (result.With == null)
			//{
			//    this.m_move.X = 0; // Objekt se nemůže tímto směrem hýbat
			//}

			//if (result.HorizontalType == IntersectionHorizontalType.FromLeft)
			//{
			//    if (result.With == null)
			//    {
			//        this.m_position.X = result.Rectangle.Left - (float)this.Sprite.FrameWidth;
			//    }
			//    else
			//    {
			//        this.m_position.X = result.With.Position.X - (float)this.Sprite.FrameWidth;
			//    }
			//}
			//else
			//{
			//    if (result.With == null)
			//    {
			//        this.m_position.X = result.Rectangle.Right;
			//    }
			//    else
			//    {
			//        this.m_position.X = result.With.Position.X + (float)result.With.Sprite.FrameHeight;
			//    }
			//}

			this.OnPositionChanged();
		}

		/// <summary>
		/// Metoda se volá pri kolizi v ose Y
		/// </summary>
		/// <param name="result">Výsledek kolize</param>
		internal virtual void OnVerticalCollision(IntersectionResult result)
		{
			//this.m_move.Y = 0;

			//// Pokud byla kolize ze spoda
			//if (result.VerticalType == IntersectionVerticalType.FromBottom)
			//{
			//    if (result.With == null)
			//    {
			//        this.m_position.Y = result.Rectangle.Bottom; // Korekce pozice
			//    }
			//    else
			//    {
			//        this.m_position.Y = result.With.Position.Y + (float)result.With.Sprite.FrameHeight;
			//    }
			//}
			//else
			//{
			//    if (result.With == null)
			//    {
			//        this.m_position.Y = result.Rectangle.Top - (float)this.Sprite.FrameHeight; // Korekce pozice
			//    }
			//    else
			//    {
			//        this.m_position.Y = result.With.Position.Y - (float)this.Sprite.FrameHeight;
			//    }

			//    this.m_verticalState = ObjectStateVertical.Idle;
			//    this.OnStopMovingVertical();
			//}

			//if (result.VerticalType == IntersectionVerticalType.FromTop)
			{
				this.OnStopMovingVertical();
			}

			this.OnPositionChanged();
		}

		/// <summary>
		/// Metoda vrací modifikovaný kolizní rect tohoto objektu
		/// </summary>
		/// <param name="addHorizontal">Kolik přidat v ose X</param>
		/// <param name="addVertical">Kolik přidat v ose Y</param>
		/// <returns><see cref="Rectangle"/></returns>
		internal Rectangle GetRectangle(float addHorizontal, float addVertical)
		{
			Rectangle rect = CollisionRect;
			rect.X += (int)(this.m_position.X + addHorizontal);
			rect.Y += (int)(this.m_position.Y + addVertical);

			return rect;
		}

		#endregion 

		#region Virtual Methods

		/// <summary>
		/// Metoda se volá když se objekt začne pohybovat v ose Y
		/// </summary>
		internal virtual void OnStartMovingVertical()
		{
		}

		/// <summary>
		/// Metoda se volá, když se objekt přestane phybovat v ose Y
		/// </summary>
		internal virtual void OnStopMovingVertical()
		{
		}

		/// <summary>
		/// Metoda se volá, když se objekt přestane phybovat v ose X
		/// </summary>
		internal virtual void OnStopMovingHorizontal()
		{
		}

		/// <summary>
		/// Metoda se volá když se objekt začne pohybovat v ose X
		/// </summary>
		internal virtual void OnStartMovingHorizontal()
		{
		}

		#endregion

		#region Event Handling

		/// <summary>
		/// Metoda normalizuje vektor <see cref="Move"/> pouze v ose X
		/// </summary>
		private void NormalizeVector()
		{
			if (IsStatic) return;
			if (DateTime.Now.Subtract(m_normalizeVectorTime).TotalMilliseconds < NormalizeVectorTime) return;
			m_normalizeVectorTime = DateTime.Now;
			if(this.m_behavior == null) return;
			this.m_behavior.NormalizeVector(this);
		}

		/// <summary>
		/// Metoda modifikuje vektor tohoto objektu
		/// </summary>
		protected virtual void ModifyVector()
		{
			if (DateTime.Now.Subtract(this.m_modifyVectorTime).TotalMilliseconds < this.ModifyVectorTime) return;
			this.m_modifyVectorTime = DateTime.Now;

			if (IsStatic || World.IsLoading) return;

			// Každý objekt musí mít přiřazen nějaký typ chování
			if (this.m_behavior == null)
			{
				throw new NotSupportedException("Tento objekt nemá zadané chování.");
			}

			this.m_behavior.ModifyVector(this);
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (this.m_disposed)
			{
				return;
			}

			//this.m_normalizeVector.Enabled = false;
			//this.m_modifyVector.Enabled = false;
			//this.m_modifyVector.Elapsed -= new ElapsedEventHandler(this.ModifyVector);
			//this.m_normalizeVector.Elapsed -= new ElapsedEventHandler(this.NormalizeVector);

			this.m_disposed = true;
			GC.SuppressFinalize(this);
		}

		#endregion

	    internal virtual bool CantCollide(ObjectBase obj)
		{
			return false;
		}
	}
}
